package entities;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import customerSelection.CustomerSelection;

public class Customer 
{
	private int customerId;
	private ArrayList<Transaction> transactions;
	
	//used while finding the top N customers
	private boolean isSelected;
	//the products for which this customer is selected and the corresponding value1
	private HashMap<Integer, Double> selectedForProducts;	
	//inclusive value for the product set for which the customer has been selected
	private double inclusiveValue = 0; 

	public Customer(int _customerId) 
	{
		customerId = _customerId;
	}
	
	//date - year, month (eg. 200504 i.e. 2005 April)
	public void addTransaction(ArrayList<Integer> transaction, int date)
	{
		if(transactions == null)
			transactions = new ArrayList<Transaction>();		
		transactions.add(new Transaction(transaction, date));
	}
	
	//generate the per month purchase history weights based on the purchase history
	//if this customer has a transaction in a given month then the weight is positive, else it is 0
	//the positive weight is based on how new the transaction is i.e. the newest month has weight 1, 
	//while the oldest has weight = 1/total nr. of months 
	//eg. - 5th month from the oldest would have a weight = 5/total nr. of months
//	public void generatePurchaseHistoryWeight(int newestDate, int oldestDate, int nrMonths)
//	{	
//		//create the array to store the weights
//		purchaseHistoryWeight = new double[nrMonths];	
//		
//		int currentDate = oldestDate;
//		for(int i = 0; i < nrMonths; i++)
//		{
//			if(purchaseHistory.get(currentDate) != null)
//				purchaseHistoryWeight[i] = (i+1)/nrMonths;
//
//			//update current date
//			currentDate++;
//			if(currentDate % 100 > 12)
//				currentDate = ((currentDate/100) + 1) * 100;
//		}
//		
//		purchaseHistory = null;
//	}
	
	//calculate value 1 for each product in the products collection
	private double purchaseHistoryWeight = 0;
	public void calculateValue1(Collection<Product> products, int divideByNLHS,int divideByNTotal, int usePurchaseHistory)
	{
		for (Product product : products) 
		{
			if(product.getRulesWithProductAsAntecedent() != null)
			{
				product.insertCustomerValue1(calculateValue(product, null, divideByNLHS, divideByNTotal, usePurchaseHistory), this);
			}
		}
	}
	
	//calculates the value1 for a product
	//in case a product list is provided, it ignores the rules whose consequent is a part of the product list
	//products list feature is used in inclusive value calculation
	private double calculateValue(Product product, ArrayList<Integer> productList, int divideByNLHS,int divideByNTotal, int usePurchaseHistory)
	{
		double v1 = 0;
		purchaseHistoryWeight = 0;
		
		for (Rule rule : product.getRulesWithProductAsAntecedent()) 
		{
			if(rule == null)
				System.out.println("NULL RULE!");
			
			if(productList == null || !productList.contains(rule.getConsequent()))
			{			
				int supports[] = getSupports(rule);
				if(supports[1] != 0)
				{
					int NT = supports[0];
					double confidenceRule = NT / supports[1];
					int supportRHS = supports[2];
					double tempV1 = NT * product.getUnitProfit() * (confidenceRule - supportRHS);
					
					if(divideByNLHS == 1)
						tempV1 /= rule.getAntecedents().size();
					if(divideByNTotal == 1)
						tempV1 /= transactions.size();
					
					v1 += tempV1;
				}
			}
		}
		
		if(usePurchaseHistory == 1)
			v1 *= purchaseHistoryWeight;
		
		return v1;
	}
	
	//calculates the supports of the rule in the transaction set of the customer
	//returned int array -> index 0 - support of the rule
	//						index 1 - support of the antecedants
	//						index 2 - support of the consequent
	//it also calculates the purchase history based weight -> sum of the weights of the months in which the customer has a transaction
	public int[] getSupports(Rule rule)
	{
		int countRuleSupport = 0;
		int countAntecedentSupport = 0;
		int countConsequentSupport = 0;
				
		ArrayList<Integer> alreadyUsedDates = new ArrayList<Integer>();
		
		boolean calculatePurchaseHistoryWeight = false;
		if(purchaseHistoryWeight == 0)
			calculatePurchaseHistoryWeight = true;
		
		for (Transaction transaction : transactions)
		{
			//get rule related supports
			boolean supportingTransaction = true;
			for(Integer antecedent : rule.getAntecedents())
			{
				if(!transaction.getProductSet().contains(antecedent))
				{
					supportingTransaction = false;
					break;
				}
			}
			if(supportingTransaction)
				countAntecedentSupport++;
			
			if(supportingTransaction && transaction.getProductSet().contains(rule.getConsequent()))
				countRuleSupport++;
			
			if(transaction.getProductSet().contains(rule.getConsequent()))
					countConsequentSupport++;
			
			//get purchase history related weight
			if(calculatePurchaseHistoryWeight && !alreadyUsedDates.contains(transaction.getDate()))
			{
				int dateIndex = CustomerSelection.months.indexOf(transaction.getDate());
				purchaseHistoryWeight += CustomerSelection.purchaseHistoryWeight[dateIndex];
			}
		}
		
		int supports[] = {countRuleSupport, countAntecedentSupport, countConsequentSupport/transactions.size()};
		
		return supports;
	}
	
	//this customer has already been selected for the products - selectedForProducts
	//find out how much value will be added by choosing this customer for the new product  
	public double getAddedValueDueToProduct(int newProductId)
	{
		int countAllProductsSupport = 0;
		
		for (Transaction transaction : transactions)
		{
			boolean supportingTransaction = true;
			for(Integer productId : selectedForProducts.keySet())
			{
				if(!transaction.getProductSet().contains(productId))
				{
					supportingTransaction = false;
					break;
				}
			}
			
			if(supportingTransaction && transaction.getProductSet().contains(newProductId))
				countAllProductsSupport++;
		}		
		return (countAllProductsSupport/transactions.size());
	}
	
	//used in the naive approach
	//gets the sum of the Value1 for all products for which the customer is selected
	public double getSumOfValue1OfProductsSelected()
	{
		double sum = 0;
		for(Integer productId : selectedForProducts.keySet())
		{
			sum += selectedForProducts.get(productId);
		}
		return sum;
	}
	
	//returns the average of the value1 for all the products for which this customer is selected
	public double getAverageValuee1SelectedForProducts(double newValue1)
	{
		double aggregatedValue1ForSelectedProducts = 0;
		Iterator<Integer> productIterator = selectedForProducts.keySet().iterator();
		while(productIterator.hasNext())
			aggregatedValue1ForSelectedProducts += productIterator.next();
		aggregatedValue1ForSelectedProducts += newValue1;
		return (aggregatedValue1ForSelectedProducts/(selectedForProducts.size() + 1));
	}

	public boolean isSelected() 
	{
		return isSelected;
	}
	
	public void setSelected(boolean isSelected, double value, int productId) 
	{
		this.isSelected = isSelected;
		insertSelectedForProduct(productId, value);
	}
	
	public int getCustomerId()
	{
		return customerId;
	}
	
	public int getNrProductsForWhichSelected()
	{
		return selectedForProducts.size();
	}
	
	private void insertSelectedForProduct(int productId, double value)
	{
		if(selectedForProducts == null)
			selectedForProducts = new HashMap<Integer, Double>();
		selectedForProducts.put(productId, value);
	}
	
	//calculate the inclusive value for the product set of this customer
	public double calculateInclusiveValue(int extraProduct,  int divideByNLHS, int divideByNTotal, int usePurchaseHistory)
	{
		//products for which the customer is selected
		ArrayList<Integer> products;
		if(selectedForProducts != null)
			products = new ArrayList<Integer>(selectedForProducts.keySet());
		else
			products = new ArrayList<Integer>();
		products.add(extraProduct);
		return inclusiveValue(products, divideByNLHS, divideByNTotal, usePurchaseHistory);
	}
	
	public double calculateInclusiveValue(int divideByNLHS, int divideByNTotal, int usePurchaseHistory)
	{
		//products for which the customer is selected		
		ArrayList<Integer> products = new ArrayList<Integer>(selectedForProducts.keySet());
		return inclusiveValue(products, divideByNLHS, divideByNTotal, usePurchaseHistory);
	}
	
	public void setInclusiveValue(double value)
	{
		inclusiveValue = value;
	}
	
	public double getInclusiveValue()
	{
		return inclusiveValue;
	}
	
	private double inclusiveValue(ArrayList<Integer> products, int divideByNLHS, int divideByNTotal, int usePurchaseHistory)
	{
		double v1 = 0;
		
		for (int i = 0; i < products.size(); i++) 
		{
			Product product = CustomerSelection.products.get(products.get(i));
			v1 += calculateValue(product, products, divideByNLHS, divideByNTotal, usePurchaseHistory);
		}
		
		return v1;
	}

	public ArrayList<Transaction> getTransactions() 
	{
		return transactions;
	}

	public HashMap<Integer, Double> getSelectedForProducts() 
	{
		return selectedForProducts;
	}
	
	public double getSelectedForProductsValue1(int productId)
	{
		Double value = selectedForProducts.get(productId);
		if(value == null)
			return 0;
		else
			return value.doubleValue();
	}
	
//	private double inclusiveValue(ArrayList<Integer> productsTillNow, double valueTillNow, Integer allProducts[])
//	{
//		if(productsTillNow != null && productsTillNow.size() == allProducts.length)		//all products done
//			return valueTillNow;
//		
//		if(productsTillNow == null || productsTillNow.size() == 0)			//first call
//		{
//			productsTillNow = new ArrayList<Integer>();
//			productsTillNow.add(allProducts[0]);
//			
//			double value = exclusiveValue(productsTillNow);
//			return inclusiveValue(productsTillNow, value, allProducts);
//		}
//		else
//		{
//			double value = valueTillNow;
//			
//			int newProductId = allProducts[productsTillNow.size()];
//			
//			//find all subsets of the products till now
//			ArrayList<ArrayList<Integer>> productsTillNowSubsets = generateSubsets(productsTillNow);
//			
//			//calculate the value for each subset of products till now with the additional new product
//			for(int i = 0; i < productsTillNowSubsets.size(); i++)
//			{
//				ArrayList<Integer> productsWithNewProduct = productsTillNowSubsets.get(i);
//				productsWithNewProduct.add(newProductId);
//				if(productsWithNewProduct.size() % 2 == 0)			//even number of products
//					value -= exclusiveValue(productsWithNewProduct);
//				else												//odd number of products
//					value += exclusiveValue(productsWithNewProduct);
//			}
//			
//			//recursive call to the inclusive value calculation function
//			productsTillNow.add(newProductId);
//			return inclusiveValue(productsTillNow, value, allProducts);
//		}		
//	}
//	
//	//generates all possible subsets of the given set
//	private ArrayList<ArrayList<Integer>> generateSubsets(ArrayList<Integer> set)
//	{
//		ArrayList<ArrayList<Integer>> subsets = new ArrayList<ArrayList<Integer>>();
//		
//		int setSize = set.size();
//		
//		//if number of values in set -> n
//		//number of subsets -> 2^n
//		int nrSubsets = (int)Math.pow(2, setSize);
//		
//		//subset generation ->
//			//generate the binary numbers from 0 to (2^n - 1)
//			//a subset consists the items whose index has bit 1 in the binary number
//		for(int i = 0; i < nrSubsets; i++)
//		{
//			String binary = Integer.toBinaryString(i);
//			
//			ArrayList<Integer> subset = new ArrayList<Integer>();
//			for(int j = 0; j < binary.length(); j++)
//			{
//				if(binary.charAt(binary.length() - 1 - j) == '1')
//					subset.add(set.get(j));
//			}
//			subsets.add(subset);
//		}
//		
//		
//		return subsets;
//	}
//	
//	//calculate the exclusive value of a given item set
//	private double exclusiveValue(ArrayList<Integer> products)
//	{
//		if(products.size() == 1)			//only one item -> exclusive value = value1
//		{
//			return selectedForProducts.get(products.get(0));
//		}
//		else				//value1 i.e. exclusive value for the entire item set
//		{			
//			//get all the rule set with all the products as antecedants
//			ArrayList<Rule> rulesWithProductsAsAntecedants = new ArrayList<Rule>();
//			for(Integer productId : products)
//			{
//				ArrayList<Rule> rulesThisProduct = CustomerSelection.products.get(productId).getRulesWithProductAsAntecedent();
//				
//				for(int j = 0; j < rulesThisProduct.size(); j++)
//				{
//					Rule rule = rulesThisProduct.get(j);
//					if(!rulesWithProductsAsAntecedants.contains(rule))
//					{
//						boolean useRule = true;
//						for(int i = 0; i < products.size(); i++)
//						{
//							if(!rule.containsAntecedant(products.get(i)))
//							{
//								useRule = false;
//							}
//						}
//						if(useRule)
//							rulesWithProductsAsAntecedants.add(rule);
//					}
//				}
//			}
//			
//			//calculate the value1 i.e. exclusive value for the product set
//			double v1 = 0;
//			for (Rule rule : rulesWithProductsAsAntecedants) 
//			{
//				if(rule == null)
//					System.out.println("NULL RULE!");
//				int supports[] = getSupports(rule);
//				if(supports[1] != 0)
//				{
//					int NT = supports[0];
//					double confidenceRule = NT / supports[1];
//					int supportRHS = supports[2];
//					//missing - profit of RHS
//					double tempV1 = NT * (confidenceRule - supportRHS);
//					
////					if(divideByNLHS == 1)
////						tempV1 /= rule.getAntecedents().size();
////					if(divideByNTotal == 1)
////						tempV1 /= transactions.size();
//					
//					v1 += tempV1;
//				}
//			}
//			
//			return v1;
//		}
//	}
	
}
